#include "icmp.h"
#include "ip.h"
#include <string.h>

/**
 * @brief 处理一个收到的数据包
 *        你首先要检查buf长度是否小于icmp头部长度
 *        接着，查看该报文的ICMP类型是否为回显请求，
 *        如果是，则回送一个回显应答（ping应答），需要自行封装应答包。
 * 
 *        应答包封装如下：
 *        首先调用buf_init()函数初始化txbuf，然后封装报头和数据，
 *        数据部分可以拷贝来自接收到的回显请求报文中的数据。
 *        最后将封装好的ICMP报文发送到IP层。  
 * 
 * @param buf 要处理的数据包
 * @param src_ip 源ip地址
 */

void icmp_in(buf_t *buf, uint8_t *src_ip)
{
    // TODO
    icmp_hdr_t* icmp_in = (icmp_hdr_t*) buf->data;
    icmp_hdr_t* icmp_out;

    // buf长度检查，buf长度应>=icmp头部长度
    if(buf->len < sizeof(icmp_hdr_t)) {
        return;
    }
    // 不是回显请求
    if(icmp_in->type != ICMP_TYPE_ECHO_REQUEST) {
        return;
    }
    // 回显请求，回送回显应答
    buf_copy(&txbuf, buf);  // 请求和应答数据结构一致
    icmp_out = (icmp_hdr_t*) txbuf.data;
    icmp_out->type = ICMP_TYPE_ECHO_REPLY;
    icmp_out->code = 0;

    // 校验和
    icmp_out->checksum = 0;
    icmp_out->checksum = checksum16((uint16_t*)txbuf.data, txbuf.len / 2);
    // 发送数据报
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 发送icmp不可达
 *        你需要首先调用buf_init初始化buf，长度为ICMP头部 + IP头部 + 原始IP数据报中的前8字节 
 *        填写ICMP报头首部，类型值为目的不可达
 *        填写校验和
 *        将封装好的ICMP数据报发送到IP层。
 * 
 * @param recv_buf 收到的ip数据包
 * @param src_ip 源ip地址
 * @param code icmp code，协议不可达或端口不可达
 */
void icmp_unreachable(buf_t *recv_buf, uint8_t *src_ip, icmp_code_t code)
{
    // TODO
    ip_hdr_t* ip_head = (ip_hdr_t*) recv_buf->data;
    uint16_t icmp_len = sizeof(icmp_hdr_t) + ip_head->hdr_len * IP_HDR_LEN_PER_BYTE + 8;  //ICMP头部8字节 + ip头部(ip数据报首部hdr_len*对应字节（4）) + 8(原始数据报前八字节)
    icmp_hdr_t* icmp;

    // 初始化buf
    buf_init(&txbuf, icmp_len);
    
    // IP头部+前8字节
    memcpy(txbuf.data + sizeof(icmp_hdr_t), recv_buf->data, icmp_len - sizeof(icmp_hdr_t));
    // ICMP首部
    icmp = (icmp_hdr_t*) txbuf.data;
    icmp->type = ICMP_TYPE_UNREACH;
    icmp->code = code;
    icmp->id = 0;
    icmp->seq = 0;
    // ICMP首部校验和
    icmp->checksum = 0;
    icmp->checksum =  checksum16((uint16_t*)txbuf.data, txbuf.len / 2); // 16对应2字节
    // ip层发送
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}